package com.leetcode;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * <p>
 * 【银泰面试题】 2021.3.3 面试题
 * 解题参考：https://my.oschina.net/u/4047016/blog/4753581
 * 用户在支付宝拥有多种支付方式（余额、红包、余额宝等，每种支付工具分布在不同系统），每种支付方式通过调用远程服务获取可用性。
 * 在外部资源环境不变情况下，请设计程序以最短响应时间获得尽可能多的可用支付方式列表。
 * </p>
 *
 * @author: Sunny
 * @date: 2021/3/4
 * @version: v1.0.0
 */
@Slf4j
public class AcquirePayList {

    /**
     * 补充：假定支付方式可用性咨询接口统一为
     * ConsultResult  PaymentRemoteService.isEnabled(String userId, String paymentType);
     * 返回结果
     * public class ConsultResult {
     * 咨询结果是否可用
     * private boolean isEnable;
     * 错误码
     * private String errorCode;
     * }
     * <p>
     * 过滤不可用支付方式类型，返回可用的支付方式
     *
     * @param allPaymentTypeList 支付方式类型列表
     * @return 可用支付方式类型列表
     * <p>
     * public List<String> filterDisablePayment(String userId, List<String> allPaymentTypeList){
     * //: TODO 完成此处的代码
     * }
     */

    /**
     * 定义线程池用来提交任务
     */
    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(),
            1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(10), new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            AtomicInteger integer = new AtomicInteger();
            thread.setDaemon(true);
            thread.setName("pay-thread-" + integer.getAndIncrement());
            return thread;
        }
    });

    /**
     * 解题思路：
     * 1：遍历方案：遍历查询所有支付方式的可用性，然后返回。Max(ResponseTime)=就是多次调用远程服务的总和。
     * 支付方式可用性在一段时间内不怎么会变化的，所以我们可以在第一种的方式基础上增加缓存保存之前的调用结果。
     * 2：缓存方案：只在前面查询的时候耗时较长，后面查询直接走缓存，速度较快。
     * 缓存方案：数据一致性得不到保障，引用MQ，若后台支付方式突然不可用，则发送MQ消息，应用监听消息更新MQ缓存来更新缓存。
     * 缺点：第一次查询遍历查询所有支付方式的可用性，速度较慢。
     * ☆ 3：多线程查询：响应时间最大值等于单一支付最长调用那一次。(这种方案也可以增加缓存)
     */

    public List<String> filterDisablePayment(String userId, List<String> allPaymentTypeList) {
        List<Future<ConsultResult>> futures = new ArrayList<>();
        for (String type : allPaymentTypeList) {
            QueryPayTypeTask queryPayTypeTask = new QueryPayTypeTask(userId, type);
            Future<ConsultResult> future = threadPoolExecutor.submit(queryPayTypeTask);
            futures.add(future);
        }
        List<ConsultResult> consultResults = new ArrayList<>();
        for (Future<ConsultResult> future : futures) {
            try {
                ConsultResult consultResult = future.get(3, TimeUnit.SECONDS); // 因为可能存在支付服务不可用，因此需要加时间，否则其会一直导致线程阻塞。
                consultResults.add(consultResult);
            } catch (Exception e) {
                log.error("调用远程失败 or 超时.", e);
            }
        }
        // 可以通过鼠标长放在该行前，点击最左侧的小灯泡能看出来可以被转换成lambda。
        List<String> payTypeList = consultResults.stream().filter(ConsultResult::isEnable).map(ConsultResult::getPayType).collect(Collectors.toList());
        return payTypeList;
    }

    /**
     * 查询支付类型的任务
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class QueryPayTypeTask implements Callable<ConsultResult> {

        private String userId;

        private String paymentType;

        @Override
        public ConsultResult call() throws Exception {
            PaymentRemoteService paymentRemoteService = new PaymentRemoteService();
            ConsultResult consultResult = paymentRemoteService.isEnabled(userId, paymentType);
            return consultResult;
        }

    }

    public static class PaymentRemoteService {

        public ConsultResult isEnabled(String userId, String paymentType) {
            /**
             * ConsultResult consultResult = new ConsultResult();
             * 环境上将这行代码换成调用远程服务的代码。
             */
            ConsultResult consultResult = new ConsultResult();
            consultResult.setEnable(true);
            consultResult.setErrorcode("0");
            consultResult.setPayType("alipay");
            return consultResult;
        }

    }

    /**
     * <p>
     * 返回结果
     * </p>
     *
     * @author: Sunny
     * @date: 2021/3/4
     * @version: v1.0.0
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class ConsultResult {

        /**
         * 查询结果是否可用
         */
        private boolean isEnable;

        /**
         * 错误码
         */
        private String errorcode;

        /**
         * 支付类型
         */
        private String payType;

    }

}
